出处:掘金
原作者:金泽宸
表单系统的通用化设计,是支撑前端“配置化平台”、“低代码编辑器”的核心引擎
表单,是前端系统最基础但最复杂的模块之一。 它常常包含:
本篇我们将一步步实现一套通用、动态、可扩展的表单系统,覆盖以下能力:
用 JSON Schema + 渲染组件映射实现动态表单:
export interface FormSchema {
label: string
field: string
type: 'input' | 'select' | 'switch' | 'date' | 'upload'
required?: boolean
options?: Array<{ label: string; value: any }>
defaultValue?: any
rules?: Array<any>
showWhen?: (formModel) => boolean
disabledWhen?: (formModel) => boolean
}
components/
├── DynamicForm.vue # 主表单组件
├── fields/ # 各类型组件
│ ├── InputField.vue
│ ├── SelectField.vue
│ ├── SwitchField.vue
│ └── ...
hooks/
├── useFormRender.ts # 渲染逻辑封装
├── useFormModel.ts # 状态管理封装
<!-- DynamicForm.vue -->
<template>
<Form ref="formRef" :model="formModel" :rules="rules">
<FormItem
v-for="schema in visibleSchemas"
:label="schema.label"
:prop="schema.field"
>
<component
:is="getFieldComponent(schema.type)"
v-model="formModel[schema.field]"
v-bind="getFieldProps(schema)"
:disabled="isDisabled(schema)"
/>
</FormItem>
</Form>
</template>
// 渲染辅助逻辑
function getFieldComponent(type: string) {
return {
input: InputField,
select: SelectField,
switch: SwitchField,
}[type]
}
showWhen、disabledWhen
const visibleSchemas = computed(() =>
props.schema.filter((schema) =>
schema.showWhen ? schema.showWhen(formModel.value) : true
)
)
function isDisabled(schema: FormSchema) {
return schema.disabledWhen ? schema.disabledWhen(formModel.value) : false
}
配置示例:
{
field: 'username',
label: '用户名',
type: 'input',
required: true
},
{
field: 'adminCode',
label: '管理员编号',
type: 'input',
showWhen: (form) => form.role === 'admin'
}
支持默认 + 自定义:
{
field: 'email',
label: '邮箱',
type: 'input',
rules: [
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '格式不正确' },
],
}
表单验证封装:
async function validate() {
return formRef.value.validate()
}
readonly 属性控制渲染<!-- InputField.vue -->
<template>
<span v-if="readonly">{{ modelValue }}</span>
<Input v-else v-model:value="modelValue" />
</template>
DynamicForm.vue 中处理<component
:is="getFieldComponent(schema.type)"
v-model="formModel[schema.field]"
:readonly="props.readonly"
:disabled="props.readonly || isDisabled(schema)"
/>
<FormItem v-if="schema.slot">
<slot :name="schema.slot" :form="formModel" />
</FormItem>
允许使用:
<DynamicForm :schema="formSchema">
<template #customSlot="{ form }">
<Input v-model:value="form.customField" />
</template>
</DynamicForm>
后端返回:
[
{
"field": "username",
"label": "用户名",
"type": "input",
"required": true
},
{
"field": "role",
"label": "角色",
"type": "select",
"options": [
{ "label": "管理员", "value": "admin" },
{ "label": "普通用户", "value": "user" }
]
}
]
前端直接传入 schema,完成渲染。
| 优化点 | 建议方案 |
|---|---|
| schema 频繁变动 | 使用 v-memo 或 shallowReactive |
| 只读表单渲染 | 使用只读组件(纯文本)+ 懒加载字段 |
| 联动过多 | 使用 computed 缓存联动状态 |
| 多类型组件动态渲染 | 使用缓存组件工厂 (defineAsyncComponent) |
| 类型 | 说明 |
|---|---|
DynamicForm.vue |
渲染主组件 |
FormRender.ts |
生成组件映射表,支持自定义注册 |
useForm.ts |
暴露 validate/reset/setFields 等方法 |
useFormSchema.ts |
拆分 schema 过滤/联动/权限逻辑 |
FieldWrapper.vue |
可扩展插槽 + 错误提示包装器 |